﻿using System;
using System.Buffers;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
using eLibrary.Constant;
using ePipe;
using Letter.IO.Binary;

namespace Letter.Net.Kcp;

sealed class CUdpConnection : AUdpConnection
{
    private const int RETRY_COUNT = 50;
    
    public CUdpConnection(EndPoint bindRemoteEP, KcpTransportOptions options, Func<AConnection> connectionFactory, Func<IPipe<byte>> channelProvider)
        : base(options, connectionFactory, channelProvider)
    {
        this.bindRemoteEP = bindRemoteEP;
    }

    private EndPoint bindRemoteEP;
    private KcpSession transport;
    private long isConnected = Booleans.FALSE;
    private int connectId;

    protected override KcpSession GetTransport(EndPoint remoteEP)
    {
        return transport;
    }

    public override void OnActive()
    {
        base.OnActive();
        
        this.connectId = GetRandomNumber();
        var conv = CreateConv(this.connectId);
        
        this.CreateTransport(connectId, conv);
        _ = this.ConnectAsync(this.connectId);
    }

    private async Task ConnectAsync(int connectId)
    {
        int index = 0;
        while (index < RETRY_COUNT)
        {
            index++;
            if (Interlocked.Read(ref this.isConnected) == Booleans.TRUE)
            {
                return;
            }
            
            this.SendSYN(connectId);
            
            await KcpTimer.DelayAsync(TimeSpan.FromSeconds(1));
        }
    }
    
    private void CreateTransport(int connectId, uint conv)
    {
        var sender = (IUdpSender) this;
        var connection = this.ConnectionFactory();
        this.transport = new KcpSession(connectId, conv, sender, connection, this.LocalEndPoint, this.bindRemoteEP, this.Options, this.ChannelProvider);
    }

    protected override void OnMessage(EndPoint remoteEP, IPipeReader<byte> reader, ref ReadOnlySequence<byte> buf)
    {
        if (this.bindRemoteEP != remoteEP)
        {
            return;
        }

        var bytesReader = buf.ToBytesReader();
        var flag = bytesReader.ReadUInt8();
        switch ((MessageType)flag)
        {
            case MessageType.SYN:
                break;
            
            case MessageType.ACK:
                if (bytesReader.ReadableLength != 4)
                {
                    return;
                }

                var connectId = bytesReader.ReadInt32BE();
                if (connectId == this.connectId)
                {
                    Interlocked.Exchange(ref this.isConnected, Booleans.TRUE);
                }

                break;
            case MessageType.K_MSG:
                this.RcvKcpMessage(remoteEP, ref bytesReader);
                break;
            case MessageType.U_MSG:
                break;
            case MessageType.FIN:
                break;
        }
        
        reader.AdvanceTo(bytesReader.End);
    }

    private void SendSYN(int connectId)
    {
        var span = (stackalloc byte[4]);
        span.FWriteInt32BE(connectId);
        this.SendSpan(this.RemoteEndPoint, MessageType.SYN, span);
    }
    
    public override void OnException(Exception ex)
    {
        if (this.transport != null)
        {
            this.transport.OnUdpException(ex);
        }
    }
}